package com.ddwl.common.aop;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.ddwl.common.config.DingTalkNotify;
import com.ddwl.common.constant.RedisKey;
import com.ddwl.common.exception.CommonException;
import com.ddwl.common.exception.GlobalException;
import com.ddwl.common.service.RedisService;
import com.ddwl.common.util.HttpUtil;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.Optional;

/**
 * @author Fu Zhaohui
 */
@Slf4j
@Aspect
@Component
public class LogAspect {

    @Resource
    private RedisService redisService;

    @Value("${spring.profiles.active}")
    private String profileActive;

    @Resource
    private DingTalkNotify dingTalkNotify;

    @Pointcut("within(com.ddwl..*.controller..*)")
    public void controllerAspect() {
        // DO AOP Log
    }

    /**
     * 前置通知 用于拦截Controller层记录用户的操作
     *
     * @param joinPoint 切点
     */
    @Before("controllerAspect()")
    public void doBefore(JoinPoint joinPoint) {
        log.info(this.handleArgs(joinPoint, null));
    }

    /**
     * 异常通知 用于拦截service层记录异常日志
     *
     * @param joinPoint
     * @param e
     */
    @AfterThrowing(pointcut = "controllerAspect()", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, Throwable e) {
        try {
            log.error(this.handleArgs(joinPoint, e));
            if (!(e instanceof CommonException)&&!(e instanceof GlobalException)&&("test".equals(profileActive)||"prod".equals(profileActive))) {
                this.notifyException(joinPoint, e);
            }
        } catch (Exception ex) {
            log.error("\naspect error", ex);
        }
    }

    /**
     * 处理请求参数
     *
     * @param joinPoint
     * @param e
     * @return
     */
    private String handleArgs(JoinPoint joinPoint, Throwable e) {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getStaticPart().getSignature();
        Method method = methodSignature.getMethod();
        Object[] args = joinPoint.getArgs();
        Annotation[][] parameterAnnotations = method.getParameterAnnotations();
        assert args.length == parameterAnnotations.length;
        StringBuilder pathParam = new StringBuilder();
        String bodyParam = null;
        for (int argIndex = 0; argIndex < args.length; argIndex++) {
            for (Annotation annotation : parameterAnnotations[argIndex]) {
                if (annotation instanceof PathVariable) {
                    if (args[argIndex] != null) {
                        pathParam.append(args[argIndex] + "&");
                    }
                } else if (annotation instanceof RequestBody) {
                    bodyParam = JSON.toJSONString(args[argIndex], SerializerFeature.DisableCircularReferenceDetect);
                }
            }
        }
        return appendArgs(joinPoint, pathParam.toString(), handleRequestParam(), bodyParam, e);
    }

    /**
     * 处理Request里面参数
     *
     * @return
     */
    private String handleRequestParam() {
        RequestAttributes ra = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes sra = (ServletRequestAttributes) ra;
        HttpServletRequest request = sra.getRequest();
        Map<String, String[]> paramsMap = request.getParameterMap();
        if (paramsMap != null && !paramsMap.isEmpty()) {
            StringBuilder params = new StringBuilder();
            for (Map.Entry<String, String[]> entry : paramsMap.entrySet()) {
                String[] values = entry.getValue();
                if (values != null) {
                    String value;
                    if (values.length == 1) {
                        value = values[0];
                    } else {
                        value = JSON.toJSONString(values, SerializerFeature.DisableCircularReferenceDetect);
                    }
                    params.append(entry.getKey() + "=" + value + "&");
                }
            }
            return params.toString();
        }
        return null;
    }

    /**
     * 拼接参数
     *
     * @param joinPoint
     * @param pathParam
     * @param queryParam
     * @param bodyParam
     * @param e
     * @return
     */
    private String appendArgs(JoinPoint joinPoint, String pathParam, String queryParam, String bodyParam, Throwable e) {
        StringBuilder sb;
        if (e == null) {
            sb = new StringBuilder("\n######################### INFO #########################");
        } else {
            sb = new StringBuilder("\n========================= Error =========================");
            sb.append("\n异常代码:" + e.getClass().getName());
            sb.append("\n异常信息:" + e.getMessage());
        }
        MethodSignature methodSignature = (MethodSignature) joinPoint.getStaticPart().getSignature();
        Method method = methodSignature.getMethod();
        ApiOperation operation = method.getAnnotation(ApiOperation.class);
        if (operation != null) {
            sb.append("\n方法描述: " + operation.value());
        }
        sb.append("\n请求方法: " + joinPoint.getTarget().getClass().getName() + "." + joinPoint.getSignature().getName() + "()");

        RequestAttributes ra = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes sra = (ServletRequestAttributes) ra;
        HttpServletRequest request = sra.getRequest();
        String product = request.getHeader("pk");
        String token = request.getHeader("ak");
        if (StringUtils.hasText(token)) {
            Map<String, String> authMap = redisService.getHashOps().entries(RedisKey.AUTH_KEY.getKey(product, token));
            sb.append("\n请求认证: ak=" + token + "/pk="+product+"/dk=" + Optional.ofNullable(request.getHeader("dk")).orElse(request.getHeader("device_id")) + "/uid=" + authMap.get("uid"));
        }

        if (StringUtils.hasText(pathParam)) {
            sb.append("\n请求参数1: " + pathParam);
        }
        if (StringUtils.hasText(queryParam)) {
            sb.append("\n请求参数2: " + queryParam);
        }
        if (StringUtils.hasText(bodyParam)) {
            sb.append("\n请求参数3: " + bodyParam);
        }
        sb.append("\n请求来源: " + product + "-" + request.getHeader("pt"));
        sb.append("\n浏览信息: " + request.getHeader("User-Agent"));
        sb.append("\n请求IP: " + HttpUtil.getIp(request));
        return sb.toString();
    }

    /**
     * 异常钉钉通知
     *
     * @param joinPoint
     * @param e
     */
    private void notifyException(JoinPoint joinPoint, Throwable e) {
        RequestAttributes ra = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes sra = (ServletRequestAttributes) ra;
        HttpServletRequest request = sra.getRequest();
        String product = request.getHeader("pk");
        String token = request.getHeader("ak");
        StringBuilder auth = new StringBuilder(joinPoint.getTarget().getClass().getName() + "." + joinPoint.getSignature().getName() + "()");
        if (StringUtils.hasText(token)) {
            Map<String, String> authMap = redisService.getHashOps().entries(RedisKey.AUTH_KEY.getKey(product, token));
            if (authMap != null && !authMap.isEmpty()) {
                auth.append("\n  userId:" + authMap.get("uid"));
                auth.append("\n  realname:" + authMap.get("realname"));
                auth.append("\n  mobile:" + authMap.get("mobile"));
                auth.append("\n  ak:" + token);
                auth.append("\n  dk:" + authMap.get("dk"));
            }
            auth.append("\nsource:" + product + "-" + request.getHeader("pt"));
            auth.append("\nip:" + HttpUtil.getIp(request));
        }
        dingTalkNotify.exceptionNotify(e);
    }


} 